﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using CredentialsManager.Helpers;
using CredentialsManager.Properties;
using MVVMFoundation;

namespace CredentialsManager.ViewModels
{
    public class CreateUserViewModel : UserViewModel
    {
        #region Private classes

        private class CreateUserInput
        {
            public CreateUserInput(string application, string userName, string password,
                string email, string securityQuestion, string securityAnswer, bool isApproved,
                bool generatePassword, int passwordLength, int minNonAlphaChars)
            {
                this.Application = application;
                this.UserName = userName;
                this.Password = password;
                this.Email = email;

                if (!string.IsNullOrWhiteSpace(SecurityQuestion))
                {
                    this.SecurityQuestion = securityQuestion;
                }

                if (!string.IsNullOrWhiteSpace(SecurityAnswer))
                {
                    this.SecurityAnswer = securityAnswer;
                }

                this.IsApproved = isApproved;
                this.GeneratePassword = generatePassword;
                this.PasswordLength = passwordLength;
                this.MinNonAlphaChars = minNonAlphaChars;
            }

            public string Application { get; private set; }
            public string UserName { get; private set; }
            public string Password { get; private set; }
            public string Email { get; private set; }
            public string SecurityQuestion { get; private set; }
            public string SecurityAnswer { get; private set; }
            public bool IsApproved { get; private set; }
            public bool GeneratePassword { get; private set; }
            public int PasswordLength { get; private set; }
            public int MinNonAlphaChars { get; private set; }
        }

        private class CreateUserOutput
        {
            public CreateUserOutput(MembershipCreateStatus createStatus, string password,
                bool generatePassword)
            {
                this.CreateStatus = createStatus;
                this.Password = password;
                this.GeneratePassword = generatePassword;
            }

            public MembershipCreateStatus CreateStatus { get; private set; }
            public string Password { get; private set; }
            public bool GeneratePassword { get; private set; }
        }

        #endregion

        #region Private member variables

        private bool m_GeneratePassword;
        private ReadOnlyCollection<string> m_NewUsers;
        private RelayCommand m_CreateCommand;
        private BackgroundWorker m_CreateWorker;

        #endregion

        #region Constructors

        public CreateUserViewModel(string displayName,
            string application, PasswordSettings passwordSettings)
            : base(displayName, application, passwordSettings)
        {
            this.IsUserNameEditable = true;
            this.IsApproved = true;
            this.GeneratePassword = false;
            m_NewUsers = new ReadOnlyCollection<string>(new List<string>());
            m_CreateWorker = new BackgroundWorker();
            m_CreateWorker.Initialise(m_CreateWorker_DoWork, m_CreateWorker_RunWorkerCompleted,
                null, false);
        }

        public CreateUserViewModel(string application, PasswordSettings passwordSettings)
            : this(Resources.NewUserViewModelDefaultDisplayName, application, passwordSettings)
        { }

        #endregion

        #region Background worker handlers

        private void m_CreateWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            var input = e.Argument as CreateUserInput;
            Debug.Assert(input != null);

            string password = GetCorrectPassword(input);
            var proxy = ProxyFactory.Create<MembershipManagerClient, IMembershipManager>(
                ServiceAddressList.CredentialsService);

            try
            {
                var createStatus = proxy.CreateUser(input.Application, input.UserName, password,
                    input.Email, input.SecurityQuestion, input.SecurityAnswer, input.IsApproved);
                e.Result = new CreateUserOutput(createStatus, password, input.GeneratePassword);
            }
            finally
            {
                ProxyFactory.Cleanup<MembershipManagerClient, IMembershipManager>(proxy);
            }
        }

        private void m_CreateWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                ErrorHandler.HandleNonSpecificWCFException(e.Error, this.DisplayName);
            }
            else
            {
                var output = e.Result as CreateUserOutput;

                if (output.CreateStatus == MembershipCreateStatus.Success)
                {
                    NotifyPassword(output);
                    var newUsers = new List<string>(this.NewUsers);
                    newUsers.Add(this.UserName);
                    this.NewUsers = new ReadOnlyCollection<string>(newUsers);
                    ClearFields();
                }
                else
                {
                    HandleUnsuccessfulUserCreate(output.CreateStatus);
                }
            }

            this.IsLoading = false;
        }

        #endregion

        #region Public properties

        public bool GeneratePassword
        {
            get
            {
                return m_GeneratePassword;
            }
            set
            {
                m_GeneratePassword = value;
                RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());
                Validate();
            }
        }

        public ReadOnlyCollection<string> NewUsers
        {
            get
            {
                return m_NewUsers;
            }
            private set
            {
                m_NewUsers = value;
                RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());
            }
        }

        #endregion

        #region Protected methods

        protected override void Validate()
        {
            bool isValid = false;

            Debug.Assert(this.CheckPasswordValidity != null && this.PasswordValidity != null);

            if (this.GeneratePassword ||
                (this.CheckPasswordValidity.IsValid && this.PasswordValidity.IsValid))
            {
                if (this.UserName != null)
                {
                    isValid = IsEmailValid();

                    if (isValid)
                    {
                        isValid = IsQuestionAndAnswerValid();
                    }
                }
            }

            this.IsValid = isValid;
        }

        #endregion

        #region Private methods

        private string GetCorrectPassword(CreateUserInput input)
        {
            string password = input.Password;

            if (input.GeneratePassword)
            {
                var proxy = ProxyFactory.Create<PasswordManagerClient, IPasswordManager>(
                    ServiceAddressList.CredentialsService);

                try
                {
                    password = proxy.GeneratePassword(input.Application,
                        input.PasswordLength, input.MinNonAlphaChars);
                }
                finally
                {
                    ProxyFactory.Cleanup<PasswordManagerClient, IPasswordManager>(proxy);
                }
            }

            return password;
        }

        private bool IsEmailValid()
        {
            bool isValid = true;

           if (this.RequiresEmail)
            {
                isValid = (this.Email != null && this.Email.Trim() != string.Empty);
            }

            return isValid;
        }

        private bool IsQuestionAndAnswerValid()
        {
            bool isValid = true;

            if (this.RequiresQuestionAndAnswer)
            {
                isValid = (this.SecurityAnswer != null && this.SecurityAnswer.Trim() != string.Empty);

                if (isValid)
                {
                    isValid = (this.SecurityQuestion != null && this.SecurityQuestion.Trim() != string.Empty);
                }
            }

            return isValid;
        }

        private void Create()
        {
            if (!this.IsValid)
            {
                throw new InvalidOperationException(Resources.CannotCreateUserInvalidOperationExceptionMessage);
            }

            var input = new CreateUserInput(this.Application, this.UserName, this.Password, this.Email,
                this.SecurityQuestion, this.SecurityAnswer, this.IsApproved, this.GeneratePassword,
                this.PasswordSettings.MinRequiredPasswordLength,
                this.PasswordSettings.MinRequiredNonAlphanumericCharacters);

            this.IsLoading = true;
            m_CreateWorker.RunWorkerAsync(input);
        }

        private void HandleUnsuccessfulUserCreate(MembershipCreateStatus status)
        {
            string errorMessage = ConvertStatusCodeToErrorMessage(status);

            if (!string.IsNullOrEmpty(errorMessage))
            {
                IMessageBoxService messageBox = base.GetService<IMessageBoxService>();
                messageBox.Show(errorMessage, this.DisplayName, MessageBoxButton.OK,
                    MessageBoxImage.Error);
            }
        }

        private void NotifyPassword(CreateUserOutput output)
        {
            if (output.GeneratePassword)
            {
                Clipboard.SetText(output.Password);

                string message = string.Format(Resources.PasswordNotificationMessage.ToString(),
                    output.Password);
                IMessageBoxService messageBox = base.GetService<IMessageBoxService>();
                messageBox.Show(message, this.DisplayName, MessageBoxButton.OK,
                    MessageBoxImage.Information);
            }
        }

        private void ClearFields()
        {
            this.UserName = string.Empty;
            this.Password = string.Empty;
            this.CheckPassword = string.Empty;
            this.Email = string.Empty;
            this.SecurityQuestion = string.Empty;
            this.SecurityAnswer = string.Empty;
        }

        #endregion

        #region Commands

        public ICommand CreateCommand
        {
            get
            {
                return m_CreateCommand ?? (m_CreateCommand = new RelayCommand(
                    param => this.Create()));
            }
        }

        #endregion
    }
}
